package licode.unisop.realm.application.executor;

import cn.hutool.core.util.StrUtil;
import licode.unisop.client.conf.SopAccountType;
import licode.unisop.provider.api.SiCacheProvider;
import licode.unisop.provider.api.SiUserProvider;
import licode.unisop.provider.conf.SiAssetState;
import licode.unisop.provider.info.*;
import licode.unisop.provider.vo.*;
import licode.unisop.realm.application.exception.UserErrorCode;
import licode.unisop.realm.client.api.SiUserQueryService;
import licode.unisop.realm.client.api.SiUserService;
import licode.unisop.realm.client.info.SiResetPasswordIn;
import licode.unisop.realm.client.info.SiUpdatePasswordIn;
import licode.unisop.realm.client.info.SiUserAttrRemoveIn;
import licode.unisop.types.AuthConst;
import licode.unisop.types.CacheConf;
import licode.unisop.types.dp.AuthType;
import licode.unisop.types.util.IdGenUtil;
import licode.unisop.unilite.utils.EncryptUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;

/**
 * 用户数据实现接口
 *
 * @author licode
 */
@Service
public class SiUserExecutor implements SiUserService {
    @Resource
    private UserQueryUtil userQueryUtil;

    @Resource
    private SiCacheProvider siCacheProvider;

    @Resource
    private SiUserQueryService siUserQueryService;

    public String getCacheKey(String realmId, String userId) {
        return CacheConf.USER_PREFIX + realmId + userId;
    }

    public void updateUserCache(String realmId, String userId) {
        siCacheProvider.deleteObject(getCacheKey(realmId, userId));
    }

    public void saveAndUpdateAttributes(SiUserProvider provider, SiUser user, SiUser existUser) {
        if (null == existUser.getProps()) {
            provider.updateUser(user);
        } else {
            if (null == user.getProps()) {
                user.setProps(SiProps.build(existUser.getProps()));
            } else {
                existUser.getProps().forEach((key, val) -> {
                    if (!user.getProps().contain(key)) {
                        user.getProps().addProp(key, val);
                    }
                });
            }
            provider.updateUser(user);
        }

        updateUserCache(user.getRealmId(), user.getId());
    }

    public void checkUserAccount(SiUser user) {
        for (SiAccount account : user.getAccounts()) {
            if (SopAccountType.ACCOUNT.equals(account.getCategory())) {
                if (StrUtil.isBlank(account.getAccount())) {
                    account.setAccount(IdGenUtil.buildAccountNo());
                }
                return;
            }
        }
        user.getAccounts().add(SiAccount.builder()
                .category(SopAccountType.ACCOUNT)
                .account(IdGenUtil.buildAccountNo())
                .build());
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public SiUser saveUser(SiUser user) {
        SiUser existUser = null;
        SiUserProvider userProvider;
        userProvider = userQueryUtil.findUserPlugin(user.getRealmId());

        if (StrUtil.isBlank(user.getRealmId())) {
            throw UserErrorCode.USER_INVALID_PROVIDER.buildThrow();
        }

        if (StrUtil.isBlank(user.getGroupId())) {
            user.setGroupId("default");
        }

        if (StrUtil.isNotBlank(user.getId())) {
            existUser = userProvider.findUserById(UserIdInfo.builder()
                    .realmId(user.getRealmId())
                    .userId(user.getId())
                    .build());
        }

        if (null != existUser) {
            if (!Integer.valueOf(1).equals(user.getEditMode())) {
                throw UserErrorCode.USER_DONT_EDIT.buildThrow();
            }
            saveAndUpdateAttributes(userProvider, user, existUser);
        } else {
            if (user.getAccounts() == null || user.getAccounts().isEmpty()) {
                throw UserErrorCode.USER_INVALID_ACCOUNT.buildThrow();
            }

            checkUserAccount(user);

            for (SiAccount account : user.getAccounts()) {
                account.setRealmId(user.getRealmId());
                if (null != userProvider.findUserByAccount(account)) {
                    throw UserErrorCode.USER_ACCOUNT_EXIST.buildThrow();
                }
            }
            user.setId(IdGenUtil.buildUserId());
            userProvider.addUser(user);
        }

        return user;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void removeAttrOfUser(SiUserAttrRemoveIn req) {
        SiUserProvider userProvider;
        userProvider = userQueryUtil.findUserPlugin(req.getRealmId());
        SiUser user = userProvider.findUserById(UserIdInfo.builder()
                .realmId(req.getRealmId())
                .userId(req.getUserId())
                .build());
        if (null != user && null != req.getProps()) {
            req.getProps().forEach(item -> user.getProps().removeProp(item));
            userProvider.updateUser(user);
            updateUserCache(user.getRealmId(), user.getId());
        }
    }

    private SiIdentity findIdentity(SiIdentitySet identityInfo, String type) {
        for (SiIdentity datum : identityInfo.getData()) {
            if (type.equals(datum.getAuthType())) {
                return datum;
            }
        }
        return null;
    }

    @Override
    public void updateCache(SiUserId userId) {
        updateUserCache(userId.getRealmId(), userId.getUserId());
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void updatePassword(SiUpdatePasswordIn info) {
        SiIdentity identity = null;
        SiUserProvider userProvider;
        userProvider = userQueryUtil.findUserPlugin(info.getRealmId());
        SiIdentitySet identityInfo = userProvider.findIdentity(UserIdInfo.builder()
                .userId(info.getUserId())
                .realmId(info.getRealmId()).build());
        if (null == identityInfo) {
            throw UserErrorCode.USER_IDENTITY_NOT_EXIST.buildThrow();
        }

        identity = findIdentity(identityInfo, AuthType.PASSWORD.getType());

        if (null == identity) {
            throw UserErrorCode.USER_IDENTITY_NOT_EXIST.buildThrow();
        }

        if (!identity.getAuthCode().equals(info.getOldPwd())) {
            throw UserErrorCode.USER_WRONG_PASSWORD.buildThrow();
        }

        identity.setAuthCode(info.getNewPwd());
        userProvider.saveIdentitySet(identityInfo);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void resetPassword(SiResetPasswordIn info) {
        SiIdentity identity = null;
        SiUserProvider userProvider;
        userProvider = userQueryUtil.findUserPlugin(info.getRealmId());
        SiIdentitySet identityInfo = userProvider.findIdentity(UserIdInfo.builder()
                .userId(info.getUserId())
                .realmId(info.getRealmId()).build());
        if (null == identityInfo) {
            identityInfo = new SiIdentitySet();
            identityInfo.setRealmId(info.getRealmId());
            identityInfo.setUserId(info.getUserId());
        }

        if (null == identityInfo.getData()) {
            identityInfo.setData(new ArrayList<>());
        }

        identity = findIdentity(identityInfo, AuthType.PASSWORD.getType());

        if (null == identity) {
            identity = new SiIdentity();
            identity.setAuthType(AuthType.PASSWORD.getType());
            identity.setAuthData("");
            identity.setSubType("");
            identity.setAuthCode("");
        }

        // 设置默认密码
        identity.setAuthCode(EncryptUtil.encryptMd5(AuthConst.DEF_PASSWORD));

        userProvider.saveIdentitySet(identityInfo);
    }

    @Override
    public void saveAccountSet(SiAccountSet info) {
        // 校验账号是否存在
        if (null != info.getData() && !info.getData().isEmpty()) {
            for (SiAccount account : info.getData()) {
                SiUser user = siUserQueryService.lookupUserByAccount(account);
                if (null != user &&
                        !user.getId().equals(info.getUserId()) &&
                        user.getRealmId().equals(info.getRealmId())) {
                    throw UserErrorCode.USER_ACCOUNT_EXIST.buildThrow();
                }
            }
            userQueryUtil.findUserPlugin(info.getRealmId()).saveAccountSet(info);
        }
    }

    @Override
    public void saveAccount(SiAccountIn info) {
        if (null != info && info.getAccount() != null) {
            info.getAccount().setRealmId(info.getRealmId());
            SiUser user = siUserQueryService.lookupUserByAccount(info.getAccount());
            if (null != user &&
                    !user.getId().equals(info.getUserId()) &&
                    user.getRealmId().equals(info.getRealmId())) {
                throw UserErrorCode.USER_ACCOUNT_EXIST.buildThrow();
            }
            userQueryUtil.findUserPlugin(info.getRealmId()).saveAccount(info);
        }
    }

    @Override
    public void saveIdentitySet(SiIdentitySet info) {
        userQueryUtil.findUserPlugin(info.getRealmId()).saveIdentitySet(info);
    }

    @Override
    public void saveIdentity(SiIdentityIn info) {
        userQueryUtil.findUserPlugin(info.getRealmId()).saveIdentity(info);
    }

    @Override
    public void updateLoginTime(SiUserLoginTimeIn loginTimeIn) {
        userQueryUtil.findUserPlugin(loginTimeIn.getRealmId()).updateLoginTime(loginTimeIn);
    }

    @Override
    public void saveUserGroup(UserGroupSaveIn group) {
        SiUserGroup userGroup = group.getGroup();
        SiUserProvider provider = userQueryUtil.findUserPlugin(group.getRealmId());
        // 默认的分组状态不允许改变
        if (AuthConst.DEFAULT_ID.equals(group.getGroup().getId())) {
            group.getGroup().setState(SiAssetState.AT_ACTIVATE);
        }
        SiUserGroup existGrp = null;

        if (StrUtil.isNotBlank(group.getGroup().getId())) {
            existGrp = provider.lookupUserGroup(UserGroupLookupIn.builder()
                    .realmId(group.getRealmId())
                    .groupId(userGroup.getId())
                    .build());
        }

        if (null != existGrp) {
            if (null == userGroup.getProps()) {
                userGroup.setProps(existGrp.getProps());
            } else if (null != existGrp.getProps()) {
                existGrp.getProps().forEach((key, val) -> {
                    if (!userGroup.getProps().contain(key)) {
                        userGroup.getProps().addProp(key, val);
                    }
                });
            }
        }

        provider.saveUserGroup(group);
    }

    @Override
    public void assignGroupToUsers(SiGroupAssignToUsersIn req) {
        userQueryUtil.findUserPlugin(req.getRealmId()).assignGroupToUsers(req);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class, timeout = 60)
    public void assignGroupsToPlatform(SiUserGroupsAssignToPlatIn req) {
        SiUserProvider provider = userQueryUtil.findUserPlugin(req.getRealmId());
        String realmId = provider.getRealmAssignToPlatform(req.getPlatformId());
        if (StrUtil.isNotBlank(realmId) && !realmId.equals(req.getRealmId())) {
            throw UserErrorCode.REALM_HAS_ASSIGN_TO_PLATFORM.buildThrow();
        }
        provider.assignGroupsToPlatform(req);
    }

    @Override
    public void assignPlatsToUserGroup(SiPlatsAssignToUserGroupIn req) {
        userQueryUtil.findUserPlugin(req.getRealmId()).assignPlatsToUserGroup(req);
    }

    @Override
    public void clearGroupsOfPlatform(SiRealmGroupsOfPlatClearIn req) {
        userQueryUtil.findUserPlugin(req.getRealmId()).clearGroupsOfPlatform(req);
    }

    @Override
    public void saveGroupsOfPlatform(SiUserGroupsAssignToPlatIn req) {
        userQueryUtil.findUserPlugin(req.getRealmId()).saveGroupsOfPlatform(req);
    }

    @Override
    public long removeGroupFromPlatform(UserGroupRemoveFromPlatIn req) {
        return userQueryUtil.findUserPlugin(req.getRealmId()).removeGroupFromPlatform(req);
    }

    @Override
    public void removeUsersFromGroup(UsersRemoveFromGroupIn req) {
        userQueryUtil.findUserPlugin(req.getRealmId()).removeUsersFromGroup(req);
    }
}
